package dialer.ws.base.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.annotation.Transactional;

import dialer.ws.base.dao.GenericDAO;
import dialer.ws.base.exception.EntityException;
import dialer.ws.base.exception.EntityExistException;
import dialer.ws.base.exception.EntityNoFoundException;
import dialer.ws.base.query.NamedQueryParameter;
import dialer.ws.base.query.QueryParameter;
import dialer.ws.base.query.QueryParameter.TemporalType;

@Transactional
@Resource
public class GenericDAOImpl<T, PK extends Serializable> implements GenericDAO<T, PK> {

	protected final Log log = LogFactory.getLog(getClass());

	@PersistenceContext
	protected EntityManager entityManager;

	protected Class<T> type;

	@SuppressWarnings("unchecked")
	public GenericDAOImpl() {
		final Class<T> aType = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass())
				.getActualTypeArguments()[0];
		this.type = aType;
	}

	public GenericDAOImpl(final Class<T> type) {
		this.type = type;
	}

	@SuppressWarnings("unchecked")
	public List<T> executeJpql(final String query) throws EntityException {
		try {
			return this.entityManager.createQuery(query).getResultList();
		} catch (final Exception e) {
			throw new EntityException(e);
		}

	}

	@SuppressWarnings("unchecked")
	public List<T> findAll() throws EntityException {
		try {
			final List<T> results = this.entityManager.createQuery("select o from " + this.type.getSimpleName() + " o")
					.getResultList();
			return results;
		} catch (final Exception e) {
			throw new EntityException(e);
		}

	}

	@SuppressWarnings("unchecked")
	public List<T> findByNamedQuery(final String queryName) throws EntityException {
		try {
			final Query q = this.entityManager.createNamedQuery(queryName);
			final List<T> result = q.getResultList();
			return result;
		} catch (final Exception e) {
			throw new EntityException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public List<T> findByNamedQueryNamedParameter(final String queryName, final NamedQueryParameter... args)
			throws EntityException {
		final Query q = this.getAndCreateQueryNamedParameters(queryName, args);
		final List<T> result = q.getResultList();
		return result;
	}

	@SuppressWarnings("unchecked")
	public List<T> findByNamedQueryPositionalParameter(final String queryName, final QueryParameter... args)
			throws EntityException {
		final Query q = this.getAndCreateQueryPositionalParameters(queryName, args);
		final List<T> results = q.getResultList();
		return results;
	}

	public T findByPk(final PK pk) throws EntityNoFoundException {
		try {
			return this.entityManager.find(this.type, pk);
		} catch (final Exception e) {
			throw new EntityNoFoundException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public T findInstanceByNamedQuery(final String queryName) throws EntityException {
		try {
			final Query q = this.entityManager.createNamedQuery(queryName);
			final T result = (T) q.getSingleResult();
			return result;
		} catch (final Exception e) {
			throw new EntityException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public T findInstanceByNamedQueryNamedParameter(final String queryName, final NamedQueryParameter... args)
			throws EntityException {
		final Query q = this.getAndCreateQueryNamedParameters(queryName, args);
		final T result = (T) q.getSingleResult();
		return result;
	}

	@SuppressWarnings("unchecked")
	public T findInstanceByNamedQueryPositionalParameter(final String queryName, final QueryParameter... args)
			throws EntityException {
		final Query q = this.getAndCreateQueryPositionalParameters(queryName, args);
		final T result = (T) q.getSingleResult();
		return result;
	}

	@SuppressWarnings("unchecked")
	public List<T> findNamedParameter(final String query, final NamedQueryParameter... args) throws EntityException {
		try {
			final Query q = this.entityManager.createQuery(query);
			this.populateQueryNamedParameter(q, args);
			final List<T> result = q.getResultList();
			return result;
		} catch (final Exception e) {
			throw new EntityException(e);
		}

	}

	@SuppressWarnings("unchecked")
	public List<T> findPositionalParameter(final String query, final QueryParameter... args) throws EntityException {
		try {
			final Query q = this.entityManager.createQuery(query);
			this.populateQueryPositionalParameters(q, args);
			final List<T> result = q.getResultList();
			return result;
		} catch (final Exception e) {
			throw new EntityException(e);
		}
	}

	@Override
	public Object findByNamedQueryNamedParameterReturnObject(String queryName, NamedQueryParameter... args)
			throws EntityException {
		final Query q = this.getAndCreateQueryNamedParameters(queryName, args);
		final Object result = (Object) q.getSingleResult();
		return result;
	}

	public void flush() throws EntityException {
		try {
			this.entityManager.flush();
		} catch (final Exception e) {
			throw new EntityException(e);
		}
	}

	private Query getAndCreateQueryNamedParameters(final String queryName, final NamedQueryParameter... args)
			throws EntityException {
		try {
			final Query q = this.entityManager.createNamedQuery(queryName);
			this.populateQueryNamedParameter(q, args);
			return q;
		} catch (final Exception e) {
			throw new EntityException(e);
		}
	}

	private Query getAndCreateQueryPositionalParameters(final String queryName, final QueryParameter... args)
			throws EntityException {
		try {
			final Query q = this.entityManager.createNamedQuery(queryName);
			this.populateQueryPositionalParameters(q, args);
			return q;
		} catch (final Exception e) {
			throw new EntityException(e);
		}
	}

	public Object merge(final Object object) throws EntityException {
		try {
			return this.entityManager.merge(object);
		} catch (final Exception e) {
			throw new EntityException(e);
		}
	}

	public Object persist(final Object object) throws EntityExistException, EntityException {
		try {
			this.entityManager.persist(object);
			return object;
		} catch (final EntityExistsException e) {
			throw new EntityExistException(e);
		} catch (final Exception e) {
			throw new EntityException(e);
		}
	}

	public Object saveOrUpdate(final Object object) throws EntityExistException, EntityException {
		try {
			if (this.entityManager.contains(object)) {
				return this.entityManager.merge(object);
			} else {
				this.entityManager.persist(object);
				return object;
			}
		} catch (final EntityExistsException e) {
			throw new EntityExistException(e);
		} catch (final Exception e) {
			throw new EntityException(e);
		}
	}

	private void populateQueryNamedParameter(final Query q, final NamedQueryParameter... args) {
		if (args != null) {

			for (final NamedQueryParameter qp : args) {
				if (qp.getType() == null || qp.getType() == TemporalType.NONE) {
					q.setParameter(qp.getParameterName(), qp.getValue());
				} else {
					switch (qp.getType()) {
					case DATE:
						if (qp.getValue() instanceof Date) {
							q.setParameter(qp.getParameterName(), (Date) qp.getValue(),
									javax.persistence.TemporalType.DATE);
						} else if (qp.getValue() instanceof Calendar) {
							q.setParameter(qp.getParameterName(), (Calendar) qp.getValue(),
									javax.persistence.TemporalType.DATE);
						} else {
							q.setParameter(qp.getParameterName(), qp.getValue());
						}
						break;
					case TIME:
						if (qp.getValue() instanceof Date) {
							q.setParameter(qp.getParameterName(), (Date) qp.getValue(),
									javax.persistence.TemporalType.TIME);
						} else if (qp.getValue() instanceof Calendar) {
							q.setParameter(qp.getParameterName(), (Calendar) qp.getValue(),
									javax.persistence.TemporalType.TIME);
						} else {
							q.setParameter(qp.getParameterName(), qp.getValue());
						}
						break;
					case TIMESTAMP:
						if (qp.getValue() instanceof Date) {
							q.setParameter(qp.getParameterName(), (Date) qp.getValue(),
									javax.persistence.TemporalType.TIMESTAMP);
						} else if (qp.getValue() instanceof Calendar) {
							q.setParameter(qp.getParameterName(), (Calendar) qp.getValue(),
									javax.persistence.TemporalType.TIMESTAMP);
						} else {
							q.setParameter(qp.getParameterName(), qp.getValue());
						}
						break;
					}
				}
			}
		}
	}

	private void populateQueryPositionalParameters(final Query q, final QueryParameter... args) {
		if (args != null) {
			int position = 1;
			for (final QueryParameter qp : args) {
				if (qp.getType() == null || qp.getType() == TemporalType.NONE) {
					q.setParameter(position++, qp.getValue());
				} else {
					switch (qp.getType()) {
					case DATE:
						if (qp.getValue() instanceof Date) {
							q.setParameter(position++, (Date) qp.getValue(), javax.persistence.TemporalType.DATE);
						} else if (qp.getValue() instanceof Calendar) {
							q.setParameter(position++, (Calendar) qp.getValue(), javax.persistence.TemporalType.DATE);
						} else {
							q.setParameter(position++, qp.getValue());
						}
						break;
					case TIME:
						if (qp.getValue() instanceof Date) {
							q.setParameter(position++, (Date) qp.getValue(), javax.persistence.TemporalType.TIME);
						} else if (qp.getValue() instanceof Calendar) {
							q.setParameter(position++, (Calendar) qp.getValue(), javax.persistence.TemporalType.TIME);
						} else {
							q.setParameter(position++, qp.getValue());
						}
						break;
					case TIMESTAMP:
						if (qp.getValue() instanceof Date) {
							q.setParameter(position++, (Date) qp.getValue(), javax.persistence.TemporalType.TIMESTAMP);
						} else if (qp.getValue() instanceof Calendar) {
							q.setParameter(position++, (Calendar) qp.getValue(),
									javax.persistence.TemporalType.TIMESTAMP);
						} else {
							q.setParameter(position++, qp.getValue());
						}
						break;
					}
				}
			}
		}
	}

	public void remove(final Object object) throws EntityException {
		try {
			Object obj = object;

			if (!this.entityManager.contains(object)) {
				// if object isn't managed by EM, load it into EM
				obj = this.entityManager.merge(object);
			}
			// object is now a managed object so it can be removed.
			this.entityManager.remove(obj);
		} catch (final Exception e) {
			throw new EntityException(e);
		}

	}

	public boolean contains(final Object object) {
		return this.entityManager.contains(object);
	}

	public void clear() throws EntityException {
		try {
			this.entityManager.clear();
		} catch (final Exception e) {
			throw new EntityException(e);
		}

	}

}
